<?php

// set locale to UTF8 to correct multibyte characters
setlocale(LC_ALL, 'en_US.UTF8');

interface elFinderILogger {
        public function log($cmd, $ok, $context, $err='', $errorData = array());
}

class elFinder {
       
        /**
         * object options
         *
         * @var array
         **/
        private $_options = array(
                'root'         => '',           // path to root directory
                'URL'          => '',           // root directory URL
                'rootAlias'    => 'Home',       // display this instead of root directory name
                'disabled'     => array(),      // list of not allowed commands
                'dotFiles'     => false,        // display dot files
                'dirSize'      => true,         // count total directories sizes
                'fileMode'     => 0666,         // new files mode
                'dirMode'      => 0777,         // new folders mode
                'mimeDetect'   => 'auto',       // files mimetypes detection method (finfo, mime_content_type, linux (file -ib), bsd (file -Ib), internal (by extensions))
                'uploadAllow'  => array(),      // mimetypes which allowed to upload
                'uploadDeny'   => array(),      // mimetypes which not allowed to upload
                'uploadOrder'  => 'deny,allow', // order to proccess uploadAllow and uploadAllow options
                'imgLib'       => 'auto',       // image manipulation library (imagick, mogrify, gd)
                'tmbDir'       => '.tmb',       // directory name for image thumbnails. Set to "" to avoid thumbnails generation
                'tmbCleanProb' => 1,            // how frequiently clean thumbnails dir (0 - never, 200 - every init request)
                'tmbAtOnce'    => 5,            // number of thumbnails to generate per request
                'tmbSize'      => 48,           // images thumbnails size (px)
                'fileURL'      => true,         // display file URL in "get info"
                'dateFormat'   => 'j M Y H:i',  // file modification date format
                'logger'       => null,         // object logger
                'aclObj'       => null,         // acl object (not implemented yet)
                'aclRole'      => 'user',       // role for acl
                'defaults'     => array(        // default permisions
                        'read'   => true,
                        'write'  => true,
                        'rm'     => true
                        ),
                'perms'        => array(),      // individual folders/files permisions    
                'debug'        => false,        // send debug to client
                'archiveMimes' => array(),      // allowed archive's mimetypes to create. Leave empty for all available types.
                'archivers'    => array()       // info about archivers to use. See example below. Leave empty for auto detect
                // 'archivers' => array(
                //      'create' => array(
                //              'application/x-gzip' => array(
                //                      'cmd' => 'tar',
                //                      'argc' => '-czf',
                //                      'ext'  => 'tar.gz'
                //                      )
                //              ),
                //      'extract' => array(
                //              'application/x-gzip' => array(
                //                      'cmd'  => 'tar',
                //                      'argc' => '-xzf',
                //                      'ext'  => 'tar.gz'
                //                      ),
                //              'application/x-bzip2' => array(
                //                      'cmd'  => 'tar',
                //                      'argc' => '-xjf',
                //                      'ext'  => 'tar.bz'
                //                      )
                //              )
                //      )
                );
       
        /**
         * mapping $_GET['cmd]/$_POST['cmd] to class methods
         *
         * @var array
         **/
        private $_commands = array(
                'open'      => '_open',
                'reload'    => '_reload',
                'mkdir'     => '_mkdir',
                'mkfile'    => '_mkfile',
                'rename'    => '_rename',
                'upload'    => '_upload',
                'paste'     => '_paste',
                'rm'        => '_rm',
                'duplicate' => '_duplicate',
                'read'      => '_fread',
                'edit'      => '_edit',
                'archive'   => '_archive',
                'extract'   => '_extract',
                'resize'    => '_resize',
                'tmb'       => '_thumbnails',
                'ping'      => '_ping'
                );
               
        /**
         * List of commands to log
         *
         * @var string
         **/
        public $_loggedCommands = array('mkdir', 'mkfile', 'rename', 'upload', 'paste', 'rm', 'duplicate', 'edit', 'resize');
       
        /**
         * Context to log command
         *
         * @var string
         **/
        private $_logContext = array();
               
        /**
         * extensions/mimetypes for _mimetypeDetect = 'internal'
         *
         * @var array
         **/
        private $_mimeTypes = array(
                //applications
                'ai'    => 'application/postscript',
                'eps'   => 'application/postscript',
                'exe'   => 'application/octet-stream',
                'doc'   => 'application/vnd.ms-word',
                'xls'   => 'application/vnd.ms-excel',
                'ppt'   => 'application/vnd.ms-powerpoint',
                'pps'   => 'application/vnd.ms-powerpoint',
                'pdf'   => 'application/pdf',
                'xml'   => 'application/xml',
                'odt'   => 'application/vnd.oasis.opendocument.text',
                'swf'   => 'application/x-shockwave-flash',
                // archives
                'gz'    => 'application/x-gzip',
                'tgz'   => 'application/x-gzip',
                'bz'    => 'application/x-bzip2',
                'bz2'   => 'application/x-bzip2',
                'tbz'   => 'application/x-bzip2',
                'zip'   => 'application/zip',
                'rar'   => 'application/x-rar',
                'tar'   => 'application/x-tar',
                '7z'    => 'application/x-7z-compressed',
                // texts
                'txt'   => 'text/plain',
                'php'   => 'text/x-php',
                'html'  => 'text/html',
                'htm'   => 'text/html',
                'js'    => 'text/javascript',
                'css'   => 'text/css',
                'rtf'   => 'text/rtf',
                'rtfd'  => 'text/rtfd',
                'py'    => 'text/x-python',
                'java'  => 'text/x-java-source',
                'rb'    => 'text/x-ruby',
                'sh'    => 'text/x-shellscript',
                'pl'    => 'text/x-perl',
                'sql'   => 'text/x-sql',
                // images
                'bmp'   => 'image/x-ms-bmp',
                'jpg'   => 'image/jpeg',
                'jpeg'  => 'image/jpeg',
                'gif'   => 'image/gif',
                'png'   => 'image/png',
                'tif'   => 'image/tiff',
                'tiff'  => 'image/tiff',
                'tga'   => 'image/x-targa',
                'psd'   => 'image/vnd.adobe.photoshop',
                //audio
                'mp3'   => 'audio/mpeg',
                'mid'   => 'audio/midi',
                'ogg'   => 'audio/ogg',
                'mp4a'  => 'audio/mp4',
                'wav'   => 'audio/wav',
                'wma'   => 'audio/x-ms-wma',
                // video
                'avi'   => 'video/x-msvideo',
                'dv'    => 'video/x-dv',
                'mp4'   => 'video/mp4',
                'mpeg'  => 'video/mpeg',
                'mpg'   => 'video/mpeg',
                'mov'   => 'video/quicktime',
                'wm'    => 'video/x-ms-wmv',
                'flv'   => 'video/x-flv',
                'mkv'   => 'video/x-matroska'
                );
       
        /**
         * undocumented class variable
         *
         * @var string
         **/
        private $_time = 0;
                               
        /**
         * Additional data about error
         *
         * @var array
         **/
        private $_errorData = array();
               
        /**
         * undocumented class variable
         *
         * @var string
         **/
        private $_fakeRoot = '';
       
        /**
         * Command result to send to client
         *
         * @var array
         **/
        private $_result = array();
               
        /**
         * undocumented class variable
         *
         * @var string
         **/
        private $_today = 0;
                       
        /**
         * undocumented class variable
         *
         * @var string
         **/
        private $_yesterday = 0;
                       
        /**
         * constructor
         *
         * @param  array object options
         * @return void
         **/
        public function __construct($options=array()) {
               
                foreach ($this->_options as $k=>$v) {
                        if (isset($options[$k])) {
                                $this->_options[$k] = is_array($this->_options[$k])
                                        ? array_merge($this->_options[$k], $options[$k])
                                        : $options[$k];
                        }
                }

                if (substr($this->_options['root'], -1) == DIRECTORY_SEPARATOR) {
                        $this->_options['root'] = substr($this->_options['root'], 0, -1);
                }

                $this->_time = $this->_options['debug'] ? $this->_utime() : 0;

                $this->_fakeRoot = !$this->_options['rootAlias']
                        ? $this->_options['root']
                        : dirname($this->_options['root']).DIRECTORY_SEPARATOR.$this->_options['rootAlias'];
                       
                if (!empty($this->_options['disabled'])) {
                        $no = array('open', 'reload', 'tmb', 'ping');
                        foreach ($this->_options['disabled'] as $k => $c) {
                                if (!isset($this->_commands[$c]) || in_array($c, $no)) {
                                        unset($this->_options['disabled'][$k]);
                                } else {
                                        unset($this->_commands[$c]);
                                }
                        }
                }
                               
                if ($this->_options['tmbDir']) {
                        $tmbDir = $this->_options['root'].DIRECTORY_SEPARATOR.$this->_options['tmbDir'];
                        $this->_options['tmbDir'] = is_dir($tmbDir) || @mkdir($tmbDir, $this->_options['dirMode']) ? $tmbDir : '';
                }
                if ($this->_options['tmbDir']) {
                        if (!in_array($this->_options['imgLib'], array('imagick', 'mogrify', 'gd'))) {
                                $this->_options['imgLib'] = $this->_getImgLib();
                        }
                }
                $this->_today = mktime(0,0,0, date('m'), date('d'), date('Y'));
                $this->_yesterday = $this->_today-86400;
        }

        /**
         * Proccess client request and output json
         *
         * @return void
         **/
        public function run() {
                if (empty($this->_options['root']) || !is_dir($this->_options['root'])) {
                        exit(json_encode(array('error' => 'Invalid backend configuration')));
                }
                if (!$this->_isAllowed($this->_options['root'], 'read')) {
                        exit(json_encode(array('error' => 'Access denied')));
                }
               
                $cmd = '';
                if (!empty($_POST['cmd'])) {
                        $cmd = trim($_POST['cmd']);
                } elseif (!empty($_GET['cmd'])) {
                        $cmd = trim($_GET['cmd']);
                }
                if (!$cmd && $_SERVER["REQUEST_METHOD"] == 'POST') {
                        header("Content-Type: text/html");
                        $this->_result['error'] = 'Data exceeds the maximum allowed size';
                        exit(json_encode($this->_result));
                }
               
                if ($cmd && (empty($this->_commands[$cmd]) || !method_exists($this, $this->_commands[$cmd]))) {
                        exit(json_encode(array('error' => 'Unknown command')));
                }
               
                if (isset($_GET['init'])) {
                       
                        $ts = $this->_utime();
                        $this->_result['disabled'] = $this->_options['disabled'];
                       
                        $this->_result['params'] = array(
                                'dotFiles'   => $this->_options['dotFiles'],
                                'uplMaxSize' => ini_get('upload_max_filesize'),
                                'archives'   => array(),
                                'extract'    => array(),
                                'url'        => $this->_options['fileURL'] ? $this->_options['URL'] : ''
                                );
                        if (isset($this->_commands['archive']) || isset($this->_commands['extract'])) {
                                $this->_checkArchivers();
                                if (isset($this->_commands['archive'])) {
                                        $this->_result['params']['archives'] = $this->_options['archiveMimes'];
                                }
                                if (isset($this->_commands['extract'])) {
                                        $this->_result['params']['extract'] = array_keys($this->_options['archivers']['extract']);
                                }
                        }
                        // clean thumbnails dir
                        if ($this->_options['tmbDir']) {
                                srand((double) microtime() * 1000000);
                                if (rand(1, 200) <= $this->_options['tmbCleanProb']) {
                                        $ts2 = $this->_utime();
                                        $ls = scandir($this->_options['tmbDir']);
                                        for ($i=0, $s = count($ls); $i < $s; $i++) {
                                                if ('.' != $ls[$i] && '..' != $ls[$i]) {
                                                        @unlink($this->_options['tmbDir'].DIRECTORY_SEPARATOR.$ls[$i]);
                                                }
                                        }
                                }
                        }
                       
                }
               
                if ($cmd) {
                        $this->{$this->_commands[$cmd]}();
                } else {
                        $this->_open();
                }

                if ($this->_options['debug']) {
                        $this->_result['debug'] = array(
                                'time'       => $this->_utime() - $this->_time,
                                'mimeDetect' => $this->_options['mimeDetect'],
                                'imgLib'     => $this->_options['imgLib']
                                );
                        if ($this->_options['dirSize']) {
                                $this->_result['debug']['dirSize'] = true;
                                $this->_result['debug']['du'] = @$this->_options['du'];
                        }
                       
                }
                header("Content-Type: ".($cmd == 'upload' ? 'text/html' : 'application/json'));
                header("Connection: close");
                echo json_encode($this->_result);
               
                if (!empty($this->_options['logger']) && in_array($cmd, $this->_loggedCommands)) {
                        $this->_options['logger']->log($cmd, empty($this->_result['error']), $this->_logContext, !empty($this->_result['error']) ? $this->_result['error'] : '', !empty($this->_result['errorData']) ? $this->_result['errorData'] : array());
                }
                exit();
        }

       
        /************************************************************/
        /**                   elFinder commands                    **/
        /************************************************************/
       
        /**
         * Return current dir content to client or output file content to browser
         *
         * @return void
         **/
        private function _open()
        {
                if (isset($_GET['current'])) { // read file
                        if (empty($_GET['current'])
                        ||  empty($_GET['target'])
                        ||  false == ($dir = $this->_findDir(trim($_GET['current'])))
                        ||  false == ($file = $this->_find(trim($_GET['target']), $dir))
                        ||  is_dir($file)
                        ) {
                                header('HTTP/1.x 404 Not Found');
                                exit('File not found');
                        }
                        if (!$this->_isAllowed($dir, 'read') || !$this->_isAllowed($file, 'read')) {
                                header('HTTP/1.x 403 Access Denied');
                                exit('Access denied');
                        }
                       
                        if (filetype($file) == 'link') {
                                $file = $this->_readlink($file);
                                if (!$file || is_dir($file)) {
                                        header('HTTP/1.x 404 Not Found');
                                        exit('File not found');
                                }
                                if (!$this->_isAllowed(dirname($file), 'read') || !$this->_isAllowed($file, 'read')) {
                                        header('HTTP/1.x 403 Access Denied');
                                        exit('Access denied');
                                }
                        }
                       
                        $mime  = $this->_mimetype($file);
                        $parts = explode('/', $mime);
                        $disp  = $parts[0] == 'image' || $parts[0] == 'text' ? 'inline' : 'attachments';
                       
                        header("Content-Type: ".$mime);
                        header("Content-Disposition: ".$disp."; filename=".basename($file));
                        header("Content-Location: ".str_replace($this->_options['root'], '', $file));
                        header('Content-Transfer-Encoding: binary');
                        header("Content-Length: ".filesize($file));
                        header("Connection: close");
                        readfile($file);
                        exit();
                       
                } else { // enter directory
                        $path = $this->_options['root'];
                        if (!empty($_GET['target'])) {
                                if (false == ($p = $this->_findDir(trim($_GET['target'])))) {
                                        if (!isset($_GET['init'])) {
                                                $this->_result['error'] = 'Invalid parameters';
                                        }
                                } elseif (!$this->_isAllowed($p, 'read')) {
                                        if (!isset($_GET['init'])) {
                                                $this->_result['error'] = 'Access denied';
                                        }
                                } else {
                                        $path = $p;
                                }
                        }
                        $this->_content($path, isset($_GET['tree']));
                }
        }
       
       
        /**
         * Rename file/folder
         *
         * @return void
         **/
        private function _rename()
        {
                if (empty($_GET['current'])
                ||  empty($_GET['target'])
                ||  false == ($dir = $this->_findDir(trim($_GET['current'])))
                ||  false == ($target = $this->_find(trim($_GET['target']), $dir))
                ) {
                        $this->_result['error'] = 'File not found';
                } elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
                        $this->_result['error'] = 'Invalid name';
                } elseif (!$this->_isAllowed($dir, 'write')) {
                        $this->_result['error'] = 'Access denied';
                } elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
                        $this->_result['error'] = 'File or folder with the same name already exists';
                } elseif (!rename($target, $dir.DIRECTORY_SEPARATOR.$name)) {
                        $this->_result['error'] = 'Unable to rename file';
                } else {
                        $this->_rmTmb($target);
                        $this->_logContext['from'] = $target;
                        $this->_logContext['to']   = $dir.DIRECTORY_SEPARATOR.$name;
                        $this->_result['select']   = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
                        $this->_content($dir, is_dir($dir.DIRECTORY_SEPARATOR.$name));
                }
        }
       
       
        /**
         * Create new folder
         *
         * @return void
         **/
        private function _mkdir()
        {
                if (empty($_GET['current']) ||  false == ($dir = $this->_findDir(trim($_GET['current'])))) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_logContext['dir'] = $dir.DIRECTORY_SEPARATOR.$_GET['name'];
                if (!$this->_isAllowed($dir, 'write')) {
                        $this->_result['error'] = 'Access denied';
                } elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
                        $this->_result['error'] = 'Invalid name';
                } elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
                        $this->_result['error'] = 'File or folder with the same name already exists';
                } elseif (!@mkdir($dir.DIRECTORY_SEPARATOR.$name, $this->_options['dirMode'])) {
                        $this->_result['error'] = 'Unable to create folder';
                } else {
                        $this->_logContext['dir'] = $dir.DIRECTORY_SEPARATOR.$name;
                        $this->_result['select']  = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
                        $this->_content($dir, true);
                }
        }
       
        /**
         * Create new empty file
         *
         * @return void
         **/
        private function _mkfile()
        {
                if (empty($_GET['current'])
                ||  false == ($dir = $this->_findDir(trim($_GET['current'])))) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_logContext['file'] = $dir.DIRECTORY_SEPARATOR.$_GET['name'];
                if (!$this->_isAllowed($dir, 'write')) {
                        $this->_result['error'] = 'Access denied';
                } elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
                        $this->_result['error'] = 'Invalid name';
                } elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
                        $this->_result['error'] = 'File or folder with the same name already exists';
                } else {
                        $f = $dir.DIRECTORY_SEPARATOR.$name;
                        $this->_logContext['file'] = $f;
                        if (false != ($fp = @fopen($f, 'wb'))) {
                                fwrite($fp, "");
                                fclose($fp);
                                $this->_result['select'] = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
                                $this->_content($dir);
                        } else {
                                $this->_result['error'] = 'Unable to create file';
                        }
                }
        }
       
        /**
         * Remove files/folders
         *
         * @return void
         **/
        private function _rm()
        {
                if (empty($_GET['current'])
                ||  false == ($dir = $this->_findDir(trim($_GET['current'])))
                || (empty($_GET['targets']) || !is_array($_GET['targets']))) {
                        return $this->_result['error'] = 'Invalid parameters';
                }

                $this->_logContext['targets'] = array();
                foreach ($_GET['targets'] as $hash) {
                        if (false != ($f = $this->_find($hash, $dir))) {
                                $this->_remove($f);
                                $this->_logContext['targets'][] = $f;
                        }
                }
                if (!empty($this->_result['errorData'])) {
                        $this->_result['error'] = 'Unable to remove file';
                }
                $this->_content($dir, true);
        }
       
        /**
         * Upload files
         *
         * @return void
         **/
        private function _upload()
        {

                if (empty($_POST['current'])
                || false == ($dir = $this->_findDir(trim($_POST['current'])))) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                if (!$this->_isAllowed($dir, 'write')) {
                        return $this->_result['error'] = 'Access denied';
                }
                if (empty($_FILES['upload']))
                {
                        return $this->_result['error'] = 'No file to upload';
                }
               
                $this->_logContext['upload'] = array();
                $this->_result['select'] = array();
                $total = 0;
                for ($i=0, $s = count($_FILES['upload']['name']); $i < $s; $i++) {
                        if (!empty($_FILES['upload']['name'][$i])) {
                                $total++;
                                $this->_logContext['upload'][] = $_FILES['upload']['name'][$i];
                                if ($_FILES['upload']['error'][$i] > 0) {
                                        $error = 'Unable to upload file';
                                        switch ($_FILES['upload']['error'][$i]) {
                                                case UPLOAD_ERR_INI_SIZE:
                                                case UPLOAD_ERR_FORM_SIZE:
                                                        $error = 'File exceeds the maximum allowed filesize';
                                                        break;
                                                case UPLOAD_ERR_EXTENSION:
                                                        $error = 'Not allowed file type';
                                                        break;
                                        }
                                        $this->_errorData($_FILES['upload']['name'][$i], $error);
                                } elseif (false == ($name = $this->_checkName($_FILES['upload']['name'][$i]))) {
                                        $this->_errorData($_FILES['upload']['name'][$i], 'Invalid name');
                                } elseif (!$this->_isUploadAllow($_FILES['upload']['name'][$i], $_FILES['upload']['tmp_name'][$i])) {
                                        $this->_errorData($_FILES['upload']['name'][$i], 'Not allowed file type');                                      
                                } else {
                                        $file = $dir.DIRECTORY_SEPARATOR.$_FILES['upload']['name'][$i];
                                        if (!@move_uploaded_file($_FILES['upload']['tmp_name'][$i], $file)) {
                                                $this->_errorData($_FILES['upload']['name'][$i], 'Unable to save uploaded file');      
                                        } else {
                                                @chmod($file, $this->_options['fileMode']);
                                                $this->_result['select'][] = $this->_hash($file);
                                        }
                                }
                        }
                }
               
                $errCnt = !empty($this->_result['errorData']) ? count($this->_result['errorData']) : 0;
               
                if ($errCnt == $total) {
                        $this->_result['error'] = 'Unable to upload files';
                } else {
                        if ($errCnt>0) {
                                $this->_result['error'] = 'Some files was not uploaded';        
                        }
                        $this->_content($dir);
                }
               
        }
       
        /**
         * Copy/move files/folders
         *
         * @return void
         **/
        private function _paste()
        {
                if (empty($_GET['current'])
                || false == ($current = $this->_findDir(trim($_GET['current'])))
                || empty($_GET['src'])
                || false == ($src = $this->_findDir(trim($_GET['src'])))
                || empty($_GET['dst'])
                || false == ($dst = $this->_findDir(trim($_GET['dst'])))
                || empty($_GET['targets']) || !is_array($_GET['targets'])
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $cut = !empty($_GET['cut']);
                $this->_logContext['src']  = array();
                $this->_logContext['dest'] = $dst;
                $this->_logContext['cut']  = $cut;
               
               
                if (!$this->_isAllowed($dst, 'write') || !$this->_isAllowed($src, 'read')) {
                        return $this->_result['error'] = 'Access denied';
                }

                foreach ($_GET['targets'] as $hash) {
                        if (false == ($f = $this->_find($hash, $src))) {
                                return $this->_result['error'] = 'File not found' && $this->_content($current, true);
                        }
                        $this->_logContext['src'][] = $f;
                        $_dst = $dst.DIRECTORY_SEPARATOR.basename($f);

                        if (0 === strpos($dst, $f)) {
                                return $this->_result['error'] = 'Unable to copy into itself' && $this->_content($current, true);
                        } elseif (file_exists($_dst)) {
                                return $this->_result['error'] = 'File or folder with the same name already exists' && $this->_content($current, true);
                        } elseif ($cut && !$this->_isAllowed($f, 'rm')) {
                                return $this->_result['error'] = 'Access denied' && $this->_content($current, true);
                        }

                        if ($cut) {
                                if (!@rename($f, $_dst)) {
                                        return $this->_result['error'] = 'Unable to move files' && $this->_content($current, true);
                                } elseif (!is_dir($f)) {
                                        $this->_rmTmb($f);
                                }
                        } elseif (!$this->_copy($f, $_dst)) {
                                return $this->_result['error'] = 'Unable to copy files' && $this->_content($current, true);
                        }
                }
                $this->_content($current, true);
        }
       
        /**
         * Create file/folder copy with suffix - "copy"
         *
         * @return void
         **/
        private function _duplicate()
        {
                if (empty($_GET['current'])
                || false == ($current = $this->_findDir(trim($_GET['current'])))
                || empty($_GET['target'])
                || false == ($target = $this->_find(trim($_GET['target']), $current))
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_logContext['target'] = $target;
                if (!$this->_isAllowed($current, 'write') || !$this->_isAllowed($target, 'read')) {
                        return $this->_result['error'] = 'Access denied';
                }
                $dup = $this->_uniqueName($target);
                if (!$this->_copy($target, $dup)) {
                        return $this->_result['error'] = 'Unable to create file copy';
                }
                $this->_result['select'] = array($this->_hash($dup));
                $this->_content($current, is_dir($target));
        }
       
        /**
         * Resize image
         *
         * @return void
         **/
        private function _resize()
        {
                if (empty($_GET['current'])
                || false == ($current = $this->_findDir(trim($_GET['current'])))
                || empty($_GET['target'])
                || false == ($target = $this->_find(trim($_GET['target']), $current))
                || empty($_GET['width'])  || 0 >= ($width  = intval($_GET['width']))
                || empty($_GET['height']) || 0 >= ($height = intval($_GET['height']))
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_logContext = array(
                        'target' => $target,
                        'width'  => $width,
                        'height' => $height
                        );
                if (!$this->_isAllowed($target, 'write')) {
                        return $this->_result['error'] = 'Access denied';
                }
                if (0 !== strpos($this->_mimetype($target), 'image')) {
                        return $this->_result['error'] = 'File is not an image';
                }
                if (!$this->_resizeImg($target, $width, $height)) {
                        return $this->_result['error'] = 'Unable to resize image';
                }
                $this->_result['select'] = array($this->_hash($target));
                $this->_content($current);
        }
               
        /**
         * Create images thumbnails
         *
         * @return void
         **/
        private function _thumbnails()
        {
                if (!empty($this->_options['tmbDir']) && !empty($_GET['current']) && false != ($current = $this->_findDir(trim($_GET['current'])))) {
                        $this->_result['current'] = $this->_hash($current);
                        $this->_result['images'] = array();
                        $ls = scandir($current);
                        $cnt = 0;
                        $max = $this->_options['tmbAtOnce'] > 0 ? intval($this->_options['tmbAtOnce']) : 5;
                        for ($i=0; $i < count($ls); $i++) {
                                if ($this->_isAccepted($ls[$i])) {
                                        $path = $current.DIRECTORY_SEPARATOR.$ls[$i];
                                        if (is_readable($path) && $this->_canCreateTmb($this->_mimetype($path))) {
                                                $tmb = $this->_tmbPath($path);
                                                if (!file_exists($tmb)) {
                                                        if ($cnt>=$max) {
                                                                return $this->_result['tmb'] = true;
                                                        } elseif ($this->_tmb($path, $tmb)) {
                                                                $this->_result['images'][$this->_hash($path)] = $this->_path2url($tmb);
                                                                $cnt++;
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }
       
        /**
         * Return file content to client
         *
         * @return void
         **/
        private function _fread()
        {
                if (empty($_GET['current'])
                || false == ($current = $this->_findDir(trim($_GET['current'])))
                || empty($_GET['target'])
                || false == ($target = $this->_find(trim($_GET['target']), $current))
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                if (!$this->_isAllowed($target, 'read')) {
                        return $this->_result['error'] = 'Access denied';
                }
                $this->_result['content'] = @file_get_contents($target);
        }
       
        /**
         * Save data into text file.
         *
         * @return void
         **/
        private function _edit()
        {
                if (empty($_POST['current'])
                || false == ($current = $this->_findDir(trim($_POST['current'])))
                || empty($_POST['target'])
                || false == ($target = $this->_find(trim($_POST['target']), $current))
                || !isset($_POST['content'])
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_logContext['target'] = $target;
                if (!$this->_isAllowed($target, 'write')) {
                        return $this->_result['error'] = 'Access denied';
                }
                if (false === file_put_contents($target, trim($_POST['content']))) {
                        return $this->_result['error'] = 'Unable to write to file';
                }
                $this->_result['target'] = $this->_info($target);
                // $this->_result['select'] = array($this->_hash($target));
        }
       
        /**
         * Create archive of selected type
         *
         * @return void
         **/
        private function _archive()
        {
                $this->_checkArchivers();
                if (empty($this->_options['archivers']['create'])
                || empty($_GET['type'])
                || empty($this->_options['archivers']['create'][$_GET['type']])
                || !in_array($_GET['type'], $this->_options['archiveMimes'])) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
               
                if (empty($_GET['current'])
                ||  empty($_GET['targets'])
                || !is_array($_GET['targets'])
                ||  false == ($dir = $this->_findDir(trim($_GET['current'])))
                || !$this->_isAllowed($dir, 'write')
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
               
                $files = array();
                $argc  = '';
                foreach ($_GET['targets'] as $hash) {
                        if (false == ($f = $this->_find($hash, $dir))) {
                                return $this->_result['error'] = 'File not found';
                        }
                        $files[] = $f;
                        $argc .= escapeshellarg(basename($f)).' ';
                }
                $arc  = $this->_options['archivers']['create'][$_GET['type']];
                $name = count($files) == 1 ? basename($files[0]) : $_GET['name'];
                $name = basename($this->_uniqueName($name.'.'.$arc['ext'], ''));
               
                $cwd = getcwd();
                chdir($dir);
                $cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg($name).' '.$argc;
                exec($cmd, $o, $c);
                chdir($cwd);
                if (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
                        $this->_content($dir);
                        $this->_result['select'] = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
                } else {
                        $this->_result['error'] = 'Unable to create archive';
                }
        }
       
        /**
         * Extract files from archive
         *
         * @return void
         **/
        private function _extract()
        {
                if (empty($_GET['current'])
                || false == ($current = $this->_findDir(trim($_GET['current'])))
                || empty($_GET['target'])
                || false == ($file = $this->_find(trim($_GET['target']), $current))
                || !$this->_isAllowed($current, 'write')
                ) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $this->_checkArchivers();
                $mime = $this->_mimetype($file);
                if (empty($this->_options['archivers']['extract'][$mime])) {
                        return $this->_result['error'] = 'Invalid parameters';
                }
                $cwd = getcwd();
                $arc = $this->_options['archivers']['extract'][$mime];
                $cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg(basename($file));
                chdir(dirname($file));
                exec($cmd, $o, $c);
                chdir($cwd);
                if ($c == 0) {
                        $this->_content($current, true);
                } else {
                        $this->_result['error'] = 'Unable to extract files from archive';
                }
        }
       
       
        /**
         * Send header Connection: close. Required by safari to fix bug http://www.webmasterworld.com/macintosh_webmaster/3300569.htm
         *
         * @return void
         **/
        private function _ping()
        {
                exit(header("Connection: close"));
        }
        /************************************************************/
        /**                    "content" methods                   **/
        /************************************************************/
        /**
         * Set current dir info, content and [dirs tree]
         *
         * @param  string  $path  current dir path
         * @param  bool    $tree  set dirs tree?
         * @return void
         **/
        private function _content($path, $tree=false)
        {
                $this->_cwd($path);
                $this->_cdc($path);
                if ($tree) {
                        $this->_result['tree'] = $this->_tree($this->_options['root']);
                }
        }

        /**
         * Set current dir info
         *
         * @param  string  $path  current dir path
         * @return void
         **/
        private function _cwd($path)
        {
                $rel  = $this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($this->_options['root']);
                if ($path == $this->_options['root']) {
                        $name = $rel;
                } else {
                        $name = basename($path);
                        $rel .= DIRECTORY_SEPARATOR.substr($path, strlen($this->_options['root'])+1);
                }
                $this->_result['cwd'] = array(
                        'hash'       => $this->_hash($path),
                        'name'       => $name,
                        'mime'       => 'directory',
                        'rel'        => $rel,
                        'size'       => 0,
                        'date'       => date($this->_options['dateFormat'], filemtime($path)),
                        'read'       => true,
                        'write'      => $this->_isAllowed($path, 'write'),
                        'rm'         => $path == $this->_options['root'] ? false : $this->_isAllowed($path, 'rm')
                        );
        }

       
        /**
         * Set current dir content
         *
         * @param  string  $path  current dir path
         * @return void
         **/
        private function _cdc($path)
        {
                $dirs = $files = array();
                $ls = scandir($path);
                for ($i=0; $i < count($ls); $i++) {
                        if ($this->_isAccepted($ls[$i])) {
                                $info = $this->_info($path.DIRECTORY_SEPARATOR.$ls[$i]);
                                if ($info['mime'] == 'directory') {
                                        $dirs[] = $info;
                                } else {
                                        $files[] = $info;
                                }
                        }
                }
                $this->_result['cdc'] = array_merge($dirs, $files);
        }
       
        /**
         * Return file/folder info
         *
         * @param  string  $path  file path
         * @return array
         **/
        private function _info($path)
        {
                $type = filetype($path);
                $stat =  $type == 'link' ? lstat($path) : stat($path);
               
                if ($stat['mtime'] > $this->_today) {
                        $d = 'Today '.date('H:i', $stat['mtime']);
                } elseif ($stat['mtime'] > $this->_yesterday) {
                        $d = 'Yesterday '.date('H:i', $stat['mtime']);
                } else {
                        $d = date($this->_options['dateFormat'], $stat['mtime']);
                }
               
                $info = array(
                        'name'  => htmlspecialchars(basename($path)),
                        'hash'  => $this->_hash($path),
                        'mime'  => $type == 'dir' ? 'directory' : $this->_mimetype($path),
                        'date'  => $d,
                        'size'  => $type == 'dir' ? $this->_dirSize($path) : $stat['size'],
                        'read'  => $this->_isAllowed($path, 'read'),
                        'write' => $this->_isAllowed($path, 'write'),
                        'rm'    => $this->_isAllowed($path, 'rm'),
                        );
               
                               
                if ($type == 'link') {
                        if (false == ($lpath = $this->_readlink($path))) {
                                $info['mime'] = 'symlink-broken';
                                return $info;
                        }
                        if (is_dir($lpath)) {
                                $info['mime']  = 'directory';
                        } else {
                                $info['parent'] = $this->_hash(dirname($lpath));
                                $info['mime']   = $this->_mimetype($lpath);
                        }
                        $info['link']   = $this->_hash($lpath);
                        $info['linkTo'] = ($this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($this->_options['root'])).substr($lpath, strlen($this->_options['root']));
                        $info['read']   = $this->_isAllowed($lpath, 'read');
                        $info['write']  = $this->_isAllowed($lpath, 'write');
                        $info['rm']     = $this->_isAllowed($lpath, 'rm');
                } else {
                        $lpath = '';
                }
               
                if ($info['mime'] != 'directory') {
                        if ($this->_options['fileURL'] && $info['read']) {
                                $info['url'] = $this->_path2url($lpath ? $lpath : $path);
                        }
                       
                        if (0 === ($p = strpos($info['mime'], 'image'))) {
                                if (false != ($s = getimagesize($path))) {
                                        $info['dim'] = $s[0].'x'.$s[1];
                                }
                                if ($info['read']) {
                                        $info['resize'] = isset($info['dim']) && $this->_canCreateTmb($info['mime']);
                                        $tmb = $this->_tmbPath($path);

                                        if (file_exists($tmb)) {
                                                $info['tmb']  = $this->_path2url($tmb);
                                        } elseif ($info['resize']) {
                                                $this->_result['tmb'] = true;
                                        }
                                       
                                }
                        }
                }
                return $info;
        }
       
        /**
         * Return directory tree (multidimensional array)
         *
         * @param  string  $path  directory path
         * @return array
         **/
        private function _tree($path)
        {
                $dir = array(
                        'hash'  => $this->_hash($path),
                        'name'  => $path == $this->_options['root'] && $this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($path),
                        'read'  => $this->_isAllowed($path, 'read'),
                        'write' => $this->_isAllowed($path, 'write'),
                        'dirs'  => array()
                        );
               
                if ($dir['read'] && false != ($ls = scandir($path))) {
                        for ($i=0; $i < count($ls); $i++) {
                                $p = $path.DIRECTORY_SEPARATOR.$ls[$i];
                                if ($this->_isAccepted($ls[$i]) && filetype($p) == 'dir') {
                                        $dir['dirs'][] = $this->_tree($p);
                                }
                        }
                }
                return $dir;
        }
       
        /************************************************************/
        /**                      fs methods                        **/
        /************************************************************/

        /**
         * Return name for duplicated file/folder or new archive
         *
         * @param  string  $f       file/folder name
         * @param  string  $suffix  file name suffix
         * @return string
         **/
        private function _uniqueName($f, $suffix=' copy')
        {
                $dir  = dirname($f);
                $name = basename($f);
                $ext = '';

                if (!is_dir($f)) {
                        if (preg_match('/\.(tar\.gz|tar\.bz|tar\.bz2|[a-z0-9]{1,4})$/i', $name, $m)) {
                                $ext = '.'.$m[1];
                                $name = substr($name, 0,  strlen($name)-strlen($m[0]));
                        }
                }
               
                if (preg_match('/('.$suffix.')(\d*)$/i', $name, $m)) {
                        $i = (int)$m[2];
                        $name = substr($name, 0, strlen($name)-strlen($m[2]));
                } else {
                        $name .= $suffix;
                        $i = 0;
                        $n = $dir.DIRECTORY_SEPARATOR.$name.$ext;
                        if (!file_exists($n)) {
                                return $n;
                        }
                }
               
                while ($i++ <= 10000) {
                        $n = $dir.DIRECTORY_SEPARATOR.$name.$i.$ext;
                        if (!file_exists($n)) {
                                return $n;
                        }
                }
                return $dir.DIRECTORY_SEPARATOR.$name.md5($f).$ext;
        }

        /**
         * Remove file or folder (recursively)
         *
         * @param  string  $path  fole/folder path
         * @return void
         **/
        private function _remove($path)
        {
                if (!$this->_isAllowed($path, 'rm')) {
                        return $this->_errorData($path, 'Access denied');
                }
                if (!is_dir($path)) {
                        if (!@unlink($path)) {
                                $this->_errorData($path, 'Unable to remove file');
                        } else {
                                $this->_rmTmb($path);
                        }
                } else {
                        $ls = scandir($path);
                        for ($i=0; $i < count($ls); $i++) {
                                if ('.' != $ls[$i] && '..' != $ls[$i]) {
                                        $this->_remove($path.DIRECTORY_SEPARATOR.$ls[$i]);
                                }
                        }
                        if (!@rmdir($path)) {
                                return $this->_errorData($path, 'Unable to remove file');
                        }
                }
                return true;
        }
       
        /**
         * Copy file/folder (recursively)
         *
         * @param  string  $src  file/folder to copy
         * @param  string  $trg  destination name
         * @return bool
         **/
        private function _copy($src, $trg)
        {
                if (!$this->_isAllowed($src, 'read')) {
                        return $this->_errorData($src, 'Access denied');
                }
               
                $dir = dirname($trg);
               
                if (!$this->_isAllowed($dir, 'write')) {
                        return $this->_errorData($dir, 'Access denied');
                }
                if (file_exists($trg)) {
                        return $this->_errorData($src, 'File or folder with the same name already exists');
                }
               
                if (!is_dir($src)) {
                        if (!@copy($src, $trg)) {
                                return $this->_errorData($src, 'Unable to copy files');
                        }
                        @chmod($trg, $this->_options['fileMode']);
                } else {
                       
                        if (!@mkdir($trg, $this->_options['dirMode'])) {
                                return $this->_errorData($src, 'Unable to copy files');
                        }
                       
                        $ls = scandir($src);
                        for ($i=0; $i < count($ls); $i++) {
                                if ('.' != $ls[$i] && '..' != $ls[$i]) {
                                        $_src = $src.DIRECTORY_SEPARATOR.$ls[$i];
                                        $_trg = $trg.DIRECTORY_SEPARATOR.$ls[$i];
                                        if (is_dir($_src)) {
                                                if (!$this->_copy($_src, $_trg)) {
                                                        return $this->_errorData($_src, 'Unable to copy files');
                                                }
                                        } else {
                                                if (!@copy($_src, $_trg)) {
                                                        return $this->_errorData($_src, 'Unable to copy files');
                                                }
                                                @chmod($_trg, $this->_options['fileMode']);
                                        }
                                }
                        }
                }
                return true;
        }
       
        /**
         * Check new file name for invalid simbols. Return name if valid
         *
         * @return string  $n  file name
         * @return string
         **/
        private function _checkName($n)
        {
                $n = strip_tags(trim($n));
                if (!$this->_options['dotFiles'] && '.' == substr($n, 0, 1)) {
                        return false;
                }
                return preg_match('|^[^\\/\<\>:]+$|', $n) ? $n : false;
        }
       
        /**
         * Find folder by hash in required folder and subfolders
         *
         * @param  string  $hash  folder hash
         * @param  string  $path  folder path to search in
         * @return string
         **/
        private function _findDir($hash, $path='')
        {
                if (!$path) {
                        $path = $this->_options['root'];
                        if ($this->_hash($path) == $hash) {
                                return $path;
                        }
                }
               
                if (false != ($ls = scandir($path))) {
                        for ($i=0; $i < count($ls); $i++) {
                                $p = $path.DIRECTORY_SEPARATOR.$ls[$i];
                                if ($this->_isAccepted($ls[$i]) && is_dir($p)) {
                                        if ($this->_hash($p) == $hash || false != ($p = $this->_findDir($hash, $p))) {
                                                return $p;
                                        }
                                }
                        }
                }
        }
       
        /**
         * Find file/folder by hash in required folder
         *
         * @param  string  $hash  file/folder hash
         * @param  string  $path  folder path to search in
         **/
        private function _find($hash, $path)
        {
                if (false != ($ls = scandir($path))) {
                        for ($i=0; $i < count($ls); $i++) {
                                if ($this->_isAccepted($ls[$i])) {
                                        $p = $path.DIRECTORY_SEPARATOR.$ls[$i];
                                        if ($this->_hash($p) == $hash) {
                                                return $p;
                                        }
                                }
                        }
                }
        }
       
       
        /**
         * Return path of file on which link point to, if exists in root directory
         *
         * @param  string  $path  symlink path
         * @return string
         **/
        private function _readlink($path)
        {
                $target = readlink($path);
                if ('/' != substr($target, 0, 1)) {
                        $target = dirname($path).DIRECTORY_SEPARATOR.$target;
                }
                $target = realpath($target);
                $root   = realpath($this->_options['root']);
                return $target && file_exists($target) && 0 === strpos($target, $root) ? $target : false;
        }
       
        /**
         * Count total directory size if this allowed in options
         *
         * @param  string  $path  directory path
         * @return int
         **/
        private function _dirSize($path)
        {
                $size = 0;
                if (!$this->_options['dirSize'] || !$this->_isAllowed($path, 'read')) {
                        return filesize($path);
                }
                if (!isset($this->_options['du'])) {
                        $this->_options['du'] = function_exists('exec')
                                ? exec('du -h '.escapeshellarg(__FILE__), $o, $s) > 0 && $s == 0
                                : false;
                }
                if ($this->_options['du']) {
                        $size = intval(exec('du -k '.escapeshellarg($path)))*1024;
                } else {
                        $ls = scandir($path);
                        for ($i=0; $i < count($ls); $i++) {
                                if ($this->_isAccepted($ls[$i])) {
                                        $p = $path.DIRECTORY_SEPARATOR.$ls[$i];
                                        $size += filetype($p) == 'dir' && $this->_isAllowed($p, 'read') ? $this->_dirSize($p) : filesize($p);
                                }
                        }
                }
                return $size;
        }
       
        /**
         * Return file mimetype
         *
         * @param  string  $path  file path
         * @return string
         **/
        private function _mimetype($path)
        {
                if (empty($this->_options['mimeDetect']) || $this->_options['mimeDetect'] == 'auto') {
                        $this->_options['mimeDetect'] = $this->_getMimeDetect();
                }
               
                switch ($this->_options['mimeDetect']) {
                        case 'finfo':
                                if (empty($this->_finfo)) {
                                        $this->_finfo = finfo_open(FILEINFO_MIME);
                                }
                                $type = @finfo_file($this->_finfo, $path);
                                break;
                        case 'php':  
                                $type = mime_content_type($path);
                                break;
                        case 'linux':  
                                $type = exec('file -ib '.escapeshellarg($path));
                                break;
                        case 'bsd':  
                                $type = exec('file -Ib '.escapeshellarg($path));
                                break;
                        default:
                                $pinfo = pathinfo($path);
                                $ext = isset($pinfo['extension']) ? strtolower($pinfo['extension']) : '';
                                $type = isset($this->_mimeTypes[$ext]) ? $this->_mimeTypes[$ext] : 'unknown;';
                }
                $type = explode(';', $type);
               
                if ($this->_options['mimeDetect'] != 'internal' && $type[0] == 'application/octet-stream') {
                        $pinfo = pathinfo($path);
                        $ext = isset($pinfo['extension']) ? strtolower($pinfo['extension']) : '';
                        if (!empty($ext) && !empty($this->_mimeTypes[$ext])) {
                                $type[0] = $this->_mimeTypes[$ext];
                        }
                }
               
                return $type[0];
        }
       
        /************************************************************/
        /**                   image manipulation                   **/
        /************************************************************/

        /**
         * Create image thumbnail
         *
         * @param  string  $img  image file
         * @param  string  $tmb  thumbnail name
         * @return bool
         **/
        private function _tmb($img, $tmb)
        {
                if (false == ($s = getimagesize($img))) {
                        return false;
                }
                $tmbSize = $this->_options['tmbSize'];
                switch ($this->_options['imgLib']) {
                        case 'imagick':
                                try {
                                        $_img = new imagick($img);
                                } catch (Exception $e) {
                                        return false;
                                }
                               
                                $_img->contrastImage(1);
                                return $_img->cropThumbnailImage($tmbSize, $tmbSize) && $_img->writeImage($tmb);
                                break;
                               
                        case 'mogrify':
                                if (@copy($img, $tmb)) {
                                        list($x, $y, $size) = $this->_cropPos($s[0], $s[1]);
                                        // exec('mogrify -crop '.$size.'x'.$size.'+'.$x.'+'.$y.' -scale '.$tmbSize.'x'.$tmbSize.'! '.escapeshellarg($tmb), $o, $c);
                                        exec('mogrify -resize '.$tmbSize.'x'.$tmbSize.'^ -gravity center -extent '.$tmbSize.'x'.$tmbSize.' '.escapeshellarg($tmb), $o, $c);
                                       
                                        if (file_exists($tmb)) {
                                                return true;
                                        } elseif ($c == 0) {
                                                // find tmb for psd and animated gif
                                                $mime = $this->_mimetype($img);
                                                if ($mime == 'image/vnd.adobe.photoshop' || $mime = 'image/gif') {
                                                        $pinfo = pathinfo($tmb);
                                                        $test = $pinfo['dirname'].DIRECTORY_SEPARATOR.$pinfo['filename'].'-0.'.$pinfo['extension'];
                                                        if (file_exists($test)) {
                                                                return rename($test, $tmb);
                                                        }
                                                }
                                        }
                                }
                                break;
                       
                        case 'gd':
                                if ($s['mime'] == 'image/jpeg') {
                                        $_img = imagecreatefromjpeg($img);
                                } elseif ($s['mime'] == 'image/png') {
                                        $_img = imagecreatefrompng($img);
                                } elseif ($s['mime'] == 'image/gif') {
                                        $_img = imagecreatefromgif($img);
                                }
                                if (!$_img || false == ($_tmb = imagecreatetruecolor($tmbSize, $tmbSize))) {
                                        return false;
                                }
                                list($x, $y, $size) = $this->_cropPos($s[0], $s[1]);
                                if (!imagecopyresampled($_tmb, $_img, 0, 0, $x, $y, $tmbSize, $tmbSize, $size, $size)) {
                                        return false;
                                }
                                $r = imagepng($_tmb, $tmb, 7);
                                imagedestroy($_img);
                                imagedestroy($_tmb);
                                return $r;
                                break;
                }
        }
       
        /**
         * Remove image thumbnail
         *
         * @param  string  $img  image file
         * @return void
         **/
        private function _rmTmb($img)
        {
                if ($this->_options['tmbDir'] && false != ($tmb = $this->_tmbPath($img)) && file_exists($tmb)) {
                        @unlink($tmb);
                }
        }
       
        /**
         * Return x/y coord for crop image thumbnail
         *
         * @param  int  $w  image width
         * @param  int  $h  image height        
         * @return array
         **/
        private function _cropPos($w, $h)
        {
                $x = $y = 0;
                $size = min($w, $h);
                if ($w > $h) {
                        $x = ceil(($w - $h)/2);
                } else {
                        $y = ceil(($h - $w)/2);
                }
                return array($x, $y, $size);
        }
       
        /**
         * Resize image
         *
         * @param  string  $img  image path
         * @param  int     $w    image width
         * @param  int     $h    image height
         * @return bool
         **/
        private function _resizeImg($img, $w, $h)
        {
                if (false == ($s = getimagesize($img))) {
                        return false;
                }
               
                switch ($this->_options['imgLib']) {
                        case 'imagick':
                                if (false != ($_img = new imagick($img))) {
                                        return $_img->cropThumbnailImage($w, $h) && $_img->writeImage($img);
                                }
                                break;
                        case 'mogrify':
                                exec('mogrify -scale '.$w.'x'.$h.'! '.escapeshellarg($img), $o, $c);
                                return 0 == $c;
                                break;
                        case 'gd':
                                if ($s['mime'] == 'image/jpeg') {
                                        $_img = imagecreatefromjpeg($img);
                                } elseif ($s['mime'] = 'image/png') {
                                        $_img = imagecreatefrompng($img);
                                } elseif ($s['mime'] = 'image/gif') {
                                        $_img = imagecreatefromgif($img);
                                }
                                if (!$_img || false == ($_out = imagecreatetruecolor($w, $h))) {
                                        return false;
                                }
                                if (!imagecopyresampled($_out, $_img, 0, 0, 0, 0, $w, $h, $s[0], $s[1])) {
                                        return false;
                                }
                                if ($s['mime'] == 'image/jpeg') {
                                        $r = imagejpeg($_out, $img, 100);
                                } else if ($s['mime'] = 'image/png') {
                                        $r = imagepng($_out, $img, 7);
                                } else {
                                        $r = imagegif($_out, $img, 7);
                                }
                                imagedestroy($_img);
                                imagedestroy($_out);
                                return $r;
                                break;
                }
                               
               
        }
       
        /**
         * Return true if we can create thumbnail for file with this mimetype
         *
         * @param  string  $mime  file mimetype
         * @return bool
         **/
        private function _canCreateTmb($mime)
        {
                if ($this->_options['tmbDir'] && $this->_options['imgLib'] && 0 === strpos($mime, 'image')) {
                        if ('gd' == $this->_options['imgLib']) {
                                return $mime == 'image/jpeg' || $mime == 'image/png' || $mime == 'image/gif';
                        }
                        return true;
                }
        }
       
        /**
         * Return image thumbnail path. For thumbnail return itself
         *
         * @param  string  $path  image path
         * @return string
         **/
        private function _tmbPath($path)
        {
                $tmb = '';
                if ($this->_options['tmbDir']) {
                        $tmb = dirname($path) != $this->_options['tmbDir']
                                ? $this->_options['tmbDir'].DIRECTORY_SEPARATOR.$this->_hash($path).'.png'
                                : $path;
                }
                return $tmb;
        }
       
        /************************************************************/
        /**                       access control                   **/
        /************************************************************/
       
        /**
         * Return true if file's mimetype is allowed for upload
         *
         * @param  string  $name    file name
         * @param  string  $tmpName uploaded file tmp name
         * @return bool
         **/
        private function _isUploadAllow($name, $tmpName)
        {
                $mime  = $this->_mimetype($this->_options['mimeDetect'] != 'internal' ? $tmpName : $name);
                $allow = false;
                $deny  = false;

                if (in_array('all', $this->_options['uploadAllow'])) {
                        $allow = true;
                } else {
                        foreach ($this->_options['uploadAllow'] as $type) {
                                if (0 === strpos($mime, $type)) {
                                        $allow = true;
                                        break;
                                }
                        }
                }
               
                if (in_array('all', $this->_options['uploadDeny'])) {
                        $deny = true;
                } else {
                        foreach ($this->_options['uploadDeny'] as $type) {
                                if (0 === strpos($mime, $type)) {
                                        $deny = true;
                                        break;
                                }
                        }
                }
                return 0 === strpos($this->_options['uploadOrder'], 'allow') ? $allow && !$deny : $allow || !$deny;
        }

        /**
         * Return true if file name is not . or ..
         * If file name begins with . return value according to $this->_options['dotFiles']
         *
         * @param  string  $file  file name
         * @return bool
         **/
        private function _isAccepted($file)
        {
                if ('.' == $file || '..' == $file) {
                        return false;
                }
                if (!$this->_options['dotFiles'] && '.' == substr($file, 0, 1)) {
                        return false;
                }
                return true;
        }

        /**
         * Return true if requeired action allowed to file/folder
         *
         * @param  string  $path    file/folder path
         * @param  string  $action  action name (read/write/rm)
         * @return void
         **/
        private function _isAllowed($path, $action) {
               
                switch ($action) {
                        case 'read':
                                if (!is_readable($path)) {
                                        return false;
                                }
                                break;
                        case 'write':
                                if (!is_writable($path)) {
                                        return false;
                                }
                                break;
                        case 'rm':
                                if (!is_writable(dirname($path))) {
                                        return false;
                                }
                                break;
                }
               
                // if ($this->_options['aclObj']) {
                //      
                // }
                $path = substr($path, strlen($this->_options['root'])+1);
                // echo "$path\n";
                foreach ($this->_options['perms'] as $regex => $rules) {
                       
                        if (preg_match($regex, $path)) {
                                if (isset($rules[$action])) {
                                        return $rules[$action];
                                }
                        }
                }
                return isset($this->_options['defaults'][$action]) ? $this->_options['defaults'][$action] : false;
        }
       
        /************************************************************/
        /**                          utilites                      **/
        /************************************************************/
       
        /**
         * Return image manipalation library name
         *
         * @return string
         **/
        private function _getImgLib()
        {
                if (extension_loaded('imagick')) {
                        return 'imagick';
                } elseif (function_exists('exec')) {
                        exec('mogrify --version', $o, $c);
                        if ($c == 0) {
                                return 'mogrify';
                        }
                }
                return function_exists('gd_info') ? 'gd' : '';
        }
       
        /**
         * Return list of available archivers
         *
         * @return array
         **/
        private function _checkArchivers()
        {
                if (!function_exists('exec')) {
                        $this->_options['archivers'] = $this->_options['archive'] = array();
                        return;
                }
                $arcs = array(
                        'create'  => array(),
                        'extract' => array()
                        );
               
                exec('tar --version', $o, $ctar);
                if ($ctar == 0) {
                        $arcs['create']['application/x-tar']  = array('cmd' => 'tar', 'argc' => '-cf', 'ext' => 'tar');
                        $arcs['extract']['application/x-tar'] = array('cmd' => 'tar', 'argc' => '-xf', 'ext' => 'tar');
                        $test = exec('gzip --version', $o, $c);
                        if ($c == 0) {
                                $arcs['create']['application/x-gzip']  = array('cmd' => 'tar', 'argc' => '-czf', 'ext' => 'tgz');
                                $arcs['extract']['application/x-gzip'] = array('cmd' => 'tar', 'argc' => '-xzf', 'ext' => 'tgz');
                        }
                        $test = exec('bzip2 --version', $o, $c);
                        if ($c == 0) {
                                $arcs['create']['application/x-bzip2']  = array('cmd' => 'tar', 'argc' => '-cjf', 'ext' => 'tbz');
                                $arcs['extract']['application/x-bzip2'] = array('cmd' => 'tar', 'argc' => '-xjf', 'ext' => 'tbz');
                        }
                }
               
                exec('zip --version', $o, $c);
                if ($c == 0) {
                        $arcs['create']['application/zip']  = array('cmd' => 'zip', 'argc' => '-r9', 'ext' => 'zip');
                }
               
                exec('unzip --help', $o, $c);
                if ($c == 0) {
                        $arcs['extract']['application/zip'] = array('cmd' => 'unzip', 'argc' => '',  'ext' => 'zip');
                }
               
                exec('rar --version', $o, $c);
                if ($c == 0) {
                        $arcs['create']['application/x-rar']  = array('cmd' => 'rar', 'argc' => 'a inul', 'ext' => 'rar');
                        $arcs['extract']['application/x-rar'] = array('cmd' => 'rar', 'argc' => 'x',      'ext' => 'rar');
                } else {
                        $test = exec('unrar', $o, $c);
                        if ($c==0 || $c == 7) {
                                $arcs['extract']['application/x-rar'] = array('cmd' => 'unrar', 'argc' => 'x', 'ext' => 'rar');
                        }
                }
               
                exec('7za --help', $o, $c);
                if ($c == 0) {
                        $arcs['create']['application/x-7z-compressed']  = array('cmd' => '7za', 'argc' => 'a', 'ext' => '7z');
                        $arcs['extract']['application/x-7z-compressed'] = array('cmd' => '7za', 'argc' => 'e -y', 'ext' => '7z');
                       
                        if (empty($arcs['create']['application/x-gzip'])) {
                                $arcs['create']['application/x-gzip'] = array('cmd' => '7za', 'argc' => 'a -tgzip', 'ext' => 'tar.gz');
                        }
                        if (empty($arcs['extract']['application/x-gzip'])) {
                                $arcs['extract']['application/x-gzip'] = array('cmd' => '7za', 'argc' => 'e -tgzip -y', 'ext' => 'tar.gz');
                        }
                        if (empty($arcs['create']['application/x-bzip2'])) {
                                $arcs['create']['application/x-bzip2'] = array('cmd' => '7za', 'argc' => 'a -tbzip2', 'ext' => 'tar.bz');
                        }
                        if (empty($arcs['extract']['application/x-bzip2'])) {
                                $arcs['extract']['application/x-bzip2'] = array('cmd' => '7za', 'argc' => 'a -tbzip2 -y', 'ext' => 'tar.bz');
                        }
                        if (empty($arcs['create']['application/zip'])) {
                                $arcs['create']['application/zip'] = array('cmd' => '7za', 'argc' => 'a -tzip -l', 'ext' => 'zip');
                        }
                        if (empty($arcs['extract']['application/zip'])) {
                                $arcs['extract']['application/zip'] = array('cmd' => '7za', 'argc' => 'e -tzip -y', 'ext' => 'zip');
                        }
                        if (empty($arcs['create']['application/x-tar'])) {
                                $arcs['create']['application/x-tar'] = array('cmd' => '7za', 'argc' => 'a -ttar -l', 'ext' => 'tar');
                        }
                        if (empty($arcs['extract']['application/x-tar'])) {
                                $arcs['extract']['application/x-tar'] = array('cmd' => '7za', 'argc' => 'e -ttar -y', 'ext' => 'tar');
                        }
                }
               
                $this->_options['archivers'] = $arcs;
                foreach ($this->_options['archiveMimes'] as $k=>$mime) {
                        if (!isset($this->_options['archivers']['create'][$mime])) {
                                unset($this->_options['archiveMimes'][$k]);
                        }
                }
                if (empty($this->_options['archiveMimes'])) {
                        $this->_options['archiveMimes'] = array_keys($this->_options['archivers']['create']);
                }
        }
       
       
        /**
         * Return mimetype detect method name
         *
         * @return string
         **/
        private function _getMimeDetect()
        {
                if (class_exists('finfo')) {
                        return 'finfo';
                } elseif (function_exists('mime_content_type') && (mime_content_type(__FILE__) == 'text/x-php' || mime_content_type(__FILE__) == 'text/x-c++')) {
                        return 'mime_content_type';
                } elseif (function_exists('exec')) {
                        $type = exec('file -ib '.escapeshellarg(__FILE__));
                        if (0 === strpos($type, 'text/x-php') || 0 === strpos($type, 'text/x-c++'))
                        {
                                return 'linux';
                        }
                        $type = exec('file -Ib '.escapeshellarg(__FILE__));
                        if (0 === strpos($type, 'text/x-php') || 0 === strpos($type, 'text/x-c++'))
                        {
                                return 'bsd';
                        }
                }
                return 'internal';
        }
       
       
        /**
         * Return file path hash
         *
         * @param  string  $path
         * @return string
         **/
        private function _hash($path)
        {
                return md5($path);
        }
       
        /**
         * Return file URL
         *
         * @param  string  $path
         * @return string
         **/
        private function _path2url($path)
        {
                $dir  = substr(dirname($path), strlen($this->_options['root'])+1);
                $file = rawurlencode(basename($path));
                return $this->_options['URL'].($dir ? str_replace(DIRECTORY_SEPARATOR, '/', $dir).'/' : '').$file;
        }
       
        /**
         * Paack error message in $this->_result['errorData']
         *
         * @param string  $path  path to file
         * @param string  $msg   error message
         * @return bool always false
         **/
        private function _errorData($path, $msg)
        {
                $path = preg_replace('|^'.preg_quote($this->_options['root']).'|', $this->_fakeRoot, $path);
                if (!isset($this->_result['errorData'])) {
                        $this->_result['errorData'] = array();
                }
                $this->_result['errorData'][$path] = $msg;
                return false;
        }      
       
        private function _utime()
        {
                $time = explode(" ", microtime());
                return (double)$time[1] + (double)$time[0];
        }      
       
}


?>